
package com.infolab.aims.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.infolab.aims.GlobalConstant;
import com.infolab.aims.common.InQueryHelper;
import com.infolab.aims.common.PaginationHelper;
import com.infolab.aims.common.QueryHelper;
import com.infolab.aims.common.bean.SearchCriteria;
import com.infolab.aims.common.bean.SearchResult;
import com.infolab.aims.common.exception.DataStoreException;
import com.infolab.aims.domain.Department;
import com.infolab.aims.domain.Location;
import com.infolab.aims.domain.SystemCodeType;
import com.infolab.aims.domain.asset.AssetEquipment;
import com.infolab.aims.domain.asset.AssetEquipmentPK;
import com.infolab.aims.domain.asset.AssetModel;
import com.infolab.aims.domain.asset.AssetTransaction;
import com.infolab.aims.domain.asset.AssetTransactionItem;
import com.infolab.aims.domain.purchase.PurchaseOrder;
import com.infolab.aims.domain.purchase.PurchaseOrderDetail;
import com.infolab.aims.domain.purchase.PurchaseOrderRepair;
import com.infolab.aims.service.intf.AssetTransactionService;
import com.infolab.aims.service.intf.PurchaseOrderService;

@Service
@Transactional
public class PurchaseOrderServiceImpl implements PurchaseOrderService {
	
	Log logger = LogFactory.getLog(getClass());
	@PersistenceContext
	private EntityManager em;
	
	@Autowired
	private AssetTransactionService assetTransactionService;
	
	public void createPurchaseOrder(PurchaseOrder purchaseOrder) {
		
		logger.info("Create Purchase Order " + purchaseOrder.getPurchaseOrderCode());
		
		if(StringUtils.isNotEmpty(purchaseOrder.getPurchaseOrderCode()))
		{
			SearchCriteria searchCriteria = new SearchCriteria();
			searchCriteria.addSearchCriteria("purchaseOrderCode", purchaseOrder.getPurchaseOrderCode());
			PurchaseOrder tmp = findPurchaseOrder(searchCriteria);
			if(tmp!=null)
				throw new DataStoreException(true,"purchaseOrderCode");
		}
		
		PurchaseOrder newPurchaseOrder= new PurchaseOrder();
		
		purchaseOrder.setModifiedBy(purchaseOrder.getCreatedBy());
		
		purchaseOrder.setDateCreated(new Date());
		
		purchaseOrder.setDateModified(new Date());
		
		purchaseOrder.setPurchaseOrderStatus(PurchaseOrder.PURCHASEORDERSTATUS_NEW);
		
		BeanUtils.copyProperties(purchaseOrder,newPurchaseOrder);
		
		em.persist(newPurchaseOrder);
		
		updatePorchaseOrderItems(purchaseOrder);
		
		purchaseOrder.setPurchaseOrderId(newPurchaseOrder.getPurchaseOrderId());
	}

	public PurchaseOrder getPurchaseOrder(Integer purchaseOrderId) {
		
		PurchaseOrder purchaseOrder = em.find(PurchaseOrder.class, purchaseOrderId);

		if(purchaseOrder.isRepairPurchaseOrder())
		{
			Query query = em.createQuery("from PurchaseOrderRepair where purchaseOrder.purchaseOrderId=:purchaseOrderId");
			query.setParameter("purchaseOrderId", purchaseOrderId);
			List<PurchaseOrderRepair> purchaseOrderRepairs = query.getResultList();
			purchaseOrder.setPurchaseOrderRepairs(purchaseOrderRepairs);
		}
		else if(purchaseOrder.isNewPurchaseOrder())
		{
			Query query = em.createQuery("from PurchaseOrderDetail where purchaseOrder.purchaseOrderId=:purchaseOrderId");
			query.setParameter("purchaseOrderId", purchaseOrderId);
			List<PurchaseOrderDetail> purchaseOrderDetails = query.getResultList();
			purchaseOrder.setPurchaseOrderDetails(purchaseOrderDetails);
		}
		
		return purchaseOrder;
		
	}

	public PurchaseOrder findPurchaseOrder(SearchCriteria searchCriteria) {
		List<PurchaseOrder> purchaseOrders = findPurchaseOrders(searchCriteria);
		if(purchaseOrders!=null && purchaseOrders.size()==1)
			return purchaseOrders.get(0);
		
		return null;
	}

	public List<PurchaseOrder> findPurchaseOrders(SearchCriteria searchCriteria) {
		if(searchCriteria==null) searchCriteria = new SearchCriteria();
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<PurchaseOrder> cq = cb.createQuery(PurchaseOrder.class);
		
		Root<PurchaseOrder> root = cq.from(PurchaseOrder.class);
			
		Join<PurchaseOrder,Department> department  = null;
		if (searchCriteria.getSearchCriteria("accessDepartmentId")!= null) {
			department =
				root.join("accessDepartment", JoinType.LEFT);	
		}
		
		List<Predicate> criteria = new ArrayList<Predicate>();
		
		if (searchCriteria.getSearchCriteria("notPurchaseOrderId")!= null) {
			ParameterExpression<Integer> p = cb.parameter(Integer.class, "notPurchaseOrderId");
			criteria.add(cb.notEqual(root.get("purchaseOrderId"),  p));
		}
		if (searchCriteria.getSearchCriteria("purchaseOrderCode")!= null) {
			ParameterExpression<String> p = cb.parameter(String.class, "purchaseOrderCode");
			criteria.add(cb.equal(root.get("purchaseOrderCode"),  p));
		}
		
		if (searchCriteria.getSearchCriteria("accessDepartmentId")!= null) {
			ParameterExpression<Integer> p = cb.parameter(Integer.class, "accessDepartmentId");
			criteria.add(cb.or(cb.isNull(department.get("departmentId")),cb.equal((department.get("departmentId")), p)));
			
		}
		if (searchCriteria.getSearchCriteria("purchaseOrderStatus")!= null) {
			
			Object obj = searchCriteria.getSearchCriteria("purchaseOrderStatus");
			if(obj instanceof InQueryHelper)
			{	
				ParameterExpression<List> p = cb.parameter(List.class, "purchaseOrderStatus");
				criteria.add(cb.in(root.get("purchaseOrderStatus")).value(p));
			}
			else
			{
				ParameterExpression<String> p = cb.parameter(String.class, "purchaseOrderStatus");
				criteria.add(cb.equal(root.get("purchaseOrderStatus"),  p));
			}
		}
		
		cq.select(root);
		if (criteria.size() == 0) {
			} else if (criteria.size() == 1) {
			cq.where(criteria.get(0));
			} else {
			cq.where(cb.and(criteria.toArray(new Predicate[0])));
		}
		
		Query query = em.createQuery(cq);
		
		if (searchCriteria.getSearchCriteria("purchaseOrderCode")!= null) {
			query.setParameter("purchaseOrderCode", searchCriteria.getSearchCriteria("purchaseOrderCode"));
		}
		if (searchCriteria.getSearchCriteria("notPurchaseOrderId")!= null) {
			query.setParameter("notPurchaseOrderId", searchCriteria.getSearchCriteria("notPurchaseOrderId"));
		}
		
		if (searchCriteria.getSearchCriteria("accessDepartmentId")!= null) {
			query.setParameter("accessDepartmentId", searchCriteria.getSearchCriteria("accessDepartmentId"));
		}
		
		if (searchCriteria.getSearchCriteria("purchaseOrderStatus")!= null) {
			Object obj = searchCriteria.getSearchCriteria("purchaseOrderStatus");
			if(obj instanceof InQueryHelper)
			{
				query.setParameter("purchaseOrderStatus", ((InQueryHelper)obj).getColumnValues());
			}
			else
				query.setParameter("purchaseOrderStatus", searchCriteria.getSearchCriteria("purchaseOrderStatus"));
		}
		
		
		if(searchCriteria.getMaxRow()!=null)
			query.setMaxResults(searchCriteria.getMaxRow());
		
		return query.getResultList();
	}

	public SearchResult searchPurchaseOrder(SearchCriteria searchCriteria) {
		
		if(searchCriteria.getCurPage()==null)
			searchCriteria.setCurPage(1);
		
		PurchaseOrderPaginationHelper helper = new PurchaseOrderPaginationHelper();
		
		return helper.getSearchResultFromQueryHelper(em, searchCriteria);
	}

	public void updatePurchaseOrder(PurchaseOrder purchaseOrder) {
		
		logger.info("Update Purchase Order "+  purchaseOrder.getPurchaseOrderCode());
		
		if(StringUtils.isNotEmpty(purchaseOrder.getPurchaseOrderCode()))
		{
			SearchCriteria searchCriteria = new SearchCriteria();
			searchCriteria.addSearchCriteria("purchaseOrderCode", purchaseOrder.getPurchaseOrderCode());
			searchCriteria.addSearchCriteria("notPurchaseOrderId", purchaseOrder.getPurchaseOrderId());
			PurchaseOrder tmp = findPurchaseOrder(searchCriteria);
			if(tmp!=null)
				throw new DataStoreException(true,"purchaseOrderCode");
		}
		
		PurchaseOrder newPurchaseOrder = em.find(PurchaseOrder.class, purchaseOrder.getPurchaseOrderId());
		
		if(newPurchaseOrder.getVersion()!=purchaseOrder.getVersion())
			throw new OptimisticLockException(purchaseOrder);
		
		
		purchaseOrder.setDateModified(new Date());
		
		// don't change this
		purchaseOrder.setPurchaseOrderStatus(newPurchaseOrder.getPurchaseOrderStatus());
		
		BeanUtils.copyProperties(purchaseOrder,newPurchaseOrder);
		
		em.merge(newPurchaseOrder);
		
		updatePorchaseOrderItems(purchaseOrder);
		
		if(purchaseOrder.isRepairPurchaseOrder()) // update equipment status to repair
		{
			AssetTransaction assetTransaction = new AssetTransaction();
			
			List<AssetTransactionItem> assetTransactionItems = new ArrayList<AssetTransactionItem>();
			Location fromLocation = purchaseOrder.getLocation();
			
			assetTransaction.setAccessDepartment(purchaseOrder.getAccessDepartment());
			assetTransaction.setFromLocation(fromLocation);
			
			Location vendorLocation = getVendorRepairLocation();
			assetTransaction.setToLocation(vendorLocation);
			assetTransaction.setDateCreated(purchaseOrder.getPurchaseOrderDate());
			assetTransaction.setCreatedBy(purchaseOrder.getCreatedBy());
			assetTransaction.setApprovedBy(purchaseOrder.getCreatedBy());
			assetTransaction.setModifiedBy(purchaseOrder.getCreatedBy());
			assetTransaction.setDateModified(new Date());
			assetTransaction.setApprovedDate(new Date());
			assetTransaction.setCompanyCode(purchaseOrder.getCompanyCode());
			assetTransaction.setRemarks(purchaseOrder.getPurchaseOrderCode());
			
			List<PurchaseOrderRepair> purchaseOrderRepairs = purchaseOrder.getPurchaseOrderRepairs();
			if(purchaseOrderRepairs!=null) {
				for (int i = 0; i < purchaseOrderRepairs.size(); i++) {
					PurchaseOrderRepair purchaseOrderRepair = purchaseOrderRepairs.get(i);
					if(!purchaseOrderRepair.isDeleted())
					{
					AssetModel assetModel = purchaseOrderRepair.getAssetModel();
					String serialNo = purchaseOrderRepair.getSerialNo();

					AssetTransactionItem assetTransactionItem = new AssetTransactionItem();
					assetTransactionItem.setSerialNo(serialNo);
					assetTransactionItem.setAssetPrice(0f);
					assetTransactionItem.setAssetModel(assetModel);
					assetTransactionItem.setCompanyCode(purchaseOrder.getCompanyCode());
					assetTransactionItems.add(assetTransactionItem);
					}
					
				}
			}
			
			assetTransaction.setAssetTransactionItems(assetTransactionItems);
			
			assetTransactionService.updateRepairPurchaseOrder(assetTransaction);
		}
		
	}
	
	private Location getVendorRepairLocation()
	{
		try
		{
			Query query = em.createQuery("from SystemCodeType where systemCodeTypeCode='"+GlobalConstant.SYSTEM_CODE_TYPE_VENDOR_REPAIR_LOCATION+"'");
			SystemCodeType systemCodeType = (SystemCodeType) query.getSingleResult();
			query = em.createQuery("from Location where locationCode='"+systemCodeType.getSystemCodeTypeValue()+"'");
			Location location = (Location) query.getSingleResult();
			return location;
		}catch(NoResultException ex)
		{
			logger.error("Can't get location for vendor repair location");
		}

		return null;
	}

	
	private void updatePorchaseOrderItems(PurchaseOrder purchaseOrder)
	{
		if(purchaseOrder.isRepairPurchaseOrder())
		{
			List<PurchaseOrderRepair> purchaseOrderRepairs = purchaseOrder.getPurchaseOrderRepairs();
			if(purchaseOrderRepairs!=null) {
				for (int i = 0; i < purchaseOrderRepairs.size(); i++) {
					PurchaseOrderRepair purchaseOrderRepair = purchaseOrderRepairs.get(i);
					
					purchaseOrderRepair.setPurchaseOrder(purchaseOrder);
					
					if(purchaseOrderRepair.getPurchaseOrderRepairId()==null)
					{
						if(purchaseOrderRepair.getAssetModel()==null)
							logger.info("Nothing to do");
						
						else if(purchaseOrderRepair.isDeleted())
							logger.info("Nothing to do");		
						else
						{
							purchaseOrderRepair.setRepairStatus(PurchaseOrderRepair.REPAIRSTATUS_SENT);
							em.persist(purchaseOrderRepair);
						}
					}
					else
					{
						if(purchaseOrderRepair.isDeleted())
						{
							purchaseOrderRepair = em.getReference(PurchaseOrderRepair.class,purchaseOrderRepair.getPurchaseOrderRepairId());
							em.remove(purchaseOrderRepair);
							
							//need to revert back equipment status
							AssetEquipmentPK assetEquipmentPK = new AssetEquipmentPK(purchaseOrderRepair.getAssetModel(),purchaseOrderRepair.getSerialNo());
							AssetEquipment assetEquipment = em.find(AssetEquipment.class, assetEquipmentPK);
							assetEquipment.setAssetEquipmentStatus(AssetModel.ASSETSTATUS_FAULT);
							assetEquipment.setLocation(purchaseOrder.getLocation());
							em.merge(assetEquipment);
						}
						else
							em.merge(purchaseOrderRepair);
					}
				}
			}
		}
		else if(purchaseOrder.isNewPurchaseOrder())
		{
			List<PurchaseOrderDetail> purchaseOrderDetails = purchaseOrder.getPurchaseOrderDetails();
			if(purchaseOrderDetails!=null) {
				for (int i = 0; i < purchaseOrderDetails.size(); i++) {
					PurchaseOrderDetail purchaseOrderDetail = purchaseOrderDetails.get(i);
					
					purchaseOrderDetail.setPurchaseOrder(purchaseOrder);
					
					if(purchaseOrderDetail.getPurchaseOrderDetailId()==null)
					{
						if(purchaseOrderDetail.getAssetModel()==null)
							logger.info("Nothing to do");
						
						else if(purchaseOrderDetail.isDeleted())
							logger.info("Nothing to do");
						
						else
						{
							purchaseOrderDetail.setQuantityReceived(0);
							em.persist(purchaseOrderDetail);
						}
					}
					else
					{
						if(purchaseOrderDetail.isDeleted())
						{
							purchaseOrderDetail = em.getReference(PurchaseOrderDetail.class,purchaseOrderDetail.getPurchaseOrderDetailId());
							em.remove(purchaseOrderDetail);
						}
						else{
							
							Integer qtyReceived = purchaseOrderDetail.getQuantityReceived();
							if(qtyReceived==null){
								qtyReceived = 0;
								purchaseOrderDetail.setQuantityReceived(qtyReceived);								
							}
							em.merge(purchaseOrderDetail);
							
							
						}
					}
				}
			}
		}
	}

	public void deletePurchaseOrder(PurchaseOrder purchaseOrder) {
		// TODO Auto-generated method stub
	
		String code = purchaseOrder.getPurchaseOrderCode();
		logger.info("Delete Purchase Order "+ purchaseOrder.getPurchaseOrderCode());
		if(purchaseOrder.isRepairPurchaseOrder())
		{
			List<PurchaseOrderRepair> purchaseOrderRepairs = purchaseOrder.getPurchaseOrderRepairs();
			if(purchaseOrderRepairs!=null) {
				for (int i = 0; i < purchaseOrderRepairs.size(); i++) {
					PurchaseOrderRepair purchaseOrderRepair = purchaseOrderRepairs.get(i);
					
					purchaseOrderRepair.setPurchaseOrder(purchaseOrder);					
							purchaseOrderRepair = em.getReference(PurchaseOrderRepair.class,purchaseOrderRepair.getPurchaseOrderRepairId());
							em.remove(purchaseOrderRepair);
							
							//need to revert back equipment status
							AssetEquipmentPK assetEquipmentPK = new AssetEquipmentPK(purchaseOrderRepair.getAssetModel(),purchaseOrderRepair.getSerialNo());
							AssetEquipment assetEquipment = em.find(AssetEquipment.class, assetEquipmentPK);
							assetEquipment.setAssetEquipmentStatus(AssetModel.ASSETSTATUS_FAULT);
							assetEquipment.setLocation(purchaseOrder.getLocation());
							em.merge(assetEquipment);
												
					}
				
			}
		}
		else if(purchaseOrder.isNewPurchaseOrder())
		{
			List<PurchaseOrderDetail> purchaseOrderDetails = purchaseOrder.getPurchaseOrderDetails();
			if(purchaseOrderDetails!=null) {
				for (int i = 0; i < purchaseOrderDetails.size(); i++) {
					PurchaseOrderDetail purchaseOrderDetail = purchaseOrderDetails.get(i);
					
					purchaseOrderDetail.setPurchaseOrder(purchaseOrder);
							purchaseOrderDetail = em.getReference(PurchaseOrderDetail.class,purchaseOrderDetail.getPurchaseOrderDetailId());
							em.remove(purchaseOrderDetail);
						
					}
				
			}
		}
		em.remove(purchaseOrder);
		
		
		Query query = em.createQuery("select assetTransaction from AssetTransaction assetTransaction where assetTransaction.remarks=:poCode and assetTransaction.assetTransactionType='POR'");
		query.setParameter("poCode", code);
		List<AssetTransaction> atToDelete=  query.getResultList();
		
		if(atToDelete!=null){
	
		AssetTransaction at = em.find(AssetTransaction.class, atToDelete.get(0).getAssetTransactionId());
		List<AssetTransactionItem> atItems = at.getAssetTransactionItems();
		if(atItems!=null)
		{
			Iterator<AssetTransactionItem> i = atItems.iterator();
			while(i.hasNext())
			{
				AssetTransactionItem atItem = i.next();				
					atItem = em.getReference(AssetTransactionItem.class, atItem.getAssetTransactionItemPK());
					em.remove(atItem);					
				}
		}
		em.remove(at);
		}
		
	}
	
	
	public void closePurchaseOrder(PurchaseOrder purchaseOrder) {
		logger.info("Close Purchase Order "+  purchaseOrder.getPurchaseOrderCode());
		
		PurchaseOrder newPurchaseOrder = em.find(PurchaseOrder.class, purchaseOrder.getPurchaseOrderId());
		
		purchaseOrder.setPurchaseOrderStatus(PurchaseOrder.PURCHASEORDERSTATUS_CLOSED);
		
		BeanUtils.copyProperties(purchaseOrder,newPurchaseOrder);
		
		em.merge(newPurchaseOrder);
	}
	
	
	public static class PurchaseOrderPaginationHelper extends PaginationHelper<PurchaseOrder> {

		@Override
		public String[] getValidColumnName() {
			return new String[]{"purchaseOrderCode",

					"purchaseOrderDate","purchaseOrderStatus","vendor.vendorName","accessDepartmentId"};

		}
		
		@Override
		public String[] getValidSortName() {
			return new String[]{"purchaseOrderCode","purchaseOrderDate","vendor.vendorName","vendor.contactInfo.contactNo",
					"modifiedBy","dateModified","purchaseOrderStatus"};
		}

		@Override
		public String getDefaultSortName() {
			return "purchaseOrderDate";
		}
		
		@Override
		public QueryHelper getQueryHelper() {
			QueryHelper queryHelper = new QueryHelper();
			queryHelper.select("select purchaseOrder from PurchaseOrder purchaseOrder");
			return queryHelper;
		}
	}

}
